package com.hk.commons.http.async;

import com.hk.commons.http.utils.HttpUtils;
import com.hk.commons.util.ArrayUtils;
import com.hk.commons.util.CollectionUtils;
import com.hk.commons.util.JsonUtils;
import com.hk.commons.util.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.Timeout;

import javax.net.ssl.SSLContext;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.Future;

/**
 * http client 异步 工具类
 *
 * @author kevin
 * @date 2019-8-9 15:00
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class AsyncHttpClientUtils {

    //    private final static Lookup<AuthSchemeFactory> authSchemeFactory;
//
    private static final PoolingAsyncClientConnectionManager connectionManager;

    private static final CloseableHttpAsyncClient httpAsyncClient;

    static {
//        var ioReactorConfig =
//                IOReactorConfig.custom()
//                        .setSoKeepAlive(false)
//                        .setTcpNoDelay(true)
//                        .setSoTimeout(Timeout.ofSeconds(5))
//                        .setIoThreadCount(Runtime.getRuntime().availableProcessors())
//                        .build();


//        var sessionStrategyRegistry = RegistryBuilder.<AuthSchemeFactory>create().register("http",
//                NoopIOSessionStrategy.INSTANCE).register("https",
//                new SSLIOSessionStrategy(SSLContexts.createDefault())).build();
//
//        authSchemeFactory = RegistryBuilder.<AuthSchemeFactory>create().register(StandardAuthScheme.BASIC,
//                        new BasicSchemeFactory(StandardCharsets.UTF_8)).register(StandardAuthScheme.DIGEST,
//                        new DigestSchemeFactory(StandardCharsets.UTF_8))
//                .register(StandardAuthScheme.NTLM, new NTLMSchemeFactory())
//                .register(StandardAuthScheme.SPNEGO, new SPNegoSchemeFactory())
//                .register(StandardAuthScheme.KERBEROS, new KerberosSchemeFactory())
//                .build();
        try {
            final SSLContext sslContext = SSLContexts.custom()
                    .loadTrustMaterial(TrustAllStrategy.INSTANCE)
                    .build();

//            var ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
            connectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
                    .setMaxConnTotal(1_000)
                    .setMaxConnPerRoute(2_000)
                    .setTlsStrategy(ClientTlsStrategyBuilder.create()
                            .setSslContext(sslContext)
                            .buildAsync())
                    .setDefaultConnectionConfig(ConnectionConfig.DEFAULT)
                    .build();
            httpAsyncClient = createDefaultHttpAsyncClient();
            httpAsyncClient.start();
        } catch (Exception e) {
            throw new UnsupportedOperationException(e);
        }
//        connectionManager.setMaxTotal(500);
//        connectionManager.setDefaultMaxPerRoute(50);
//        connectionManager.setDefaultConnectionConfig(ConnectionConfig.custom().setMalformedInputAction
//                        (CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Consts.UTF_8)
//                .build());
//        ClientIdleConnectionReaper.registerNConnectionManager(connectionManager);

    }

    public static CloseableHttpAsyncClient getDefaultHttpAsyncClient() {
        return httpAsyncClient;
    }

    public static CloseableHttpAsyncClient createDefaultHttpAsyncClient() {
        return createAsyncHttpClient(HttpUtils.DEFAULT_REQUEST_CONFIG);
    }

    public static CloseableHttpAsyncClient createAsyncHttpClient(RequestConfig requestConfig) {
        return HttpAsyncClients.custom()
                .setIOReactorConfig(IOReactorConfig.custom()
                        .setSoKeepAlive(false)
                        .setTcpNoDelay(true)
                        .setSoTimeout(Timeout.ofSeconds(5))
                        .setIoThreadCount(Runtime.getRuntime().availableProcessors())
                        .build())
                .setConnectionManager(connectionManager)
//                .setDefaultAuthSchemeRegistry(authSchemeFactory)
                .setDefaultRequestConfig(requestConfig)
                .build();
    }

    /**
     * http Get 异步调用
     *
     * @param uri            请求地址
     * @param params         请求参数
     * @param futureCallback 异步回调，如果 为 null　时，直接返回结果
     * @param headers        　请求头
     * @return 结果，如果 futureCallback 不为 null，结果需要在此回调中获得
     */
    public static Future<SimpleHttpResponse> get(String uri, Map<String, ?> params,
                                                 FutureCallback<SimpleHttpResponse> futureCallback,
                                                 Header... headers) {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst());
        CollectionUtils.addAllNotNull(headerList, headers);
        var request = SimpleHttpRequest.create(Method.GET,
                URI.create(HttpUtils.generateUri(uri, params)));
        request.setHeaders(headerList.toArray(new Header[0]));
        return execute(createDefaultHttpAsyncClient(), request, futureCallback);
    }

    /**
     * http Delete 异步调用
     *
     * @param uri            请求地址
     * @param params         请求参数
     * @param futureCallback 异步回调，如果 为 null　时，直接返回结果
     * @param headers        　请求头
     * @return 结果，如果 futureCallback 不为 null，结果需要在此回调中获得
     */
    public static Future<SimpleHttpResponse> delete(String uri, Map<String, ?> params,
                                                    FutureCallback<SimpleHttpResponse> futureCallback,
                                                    Header... headers) {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst());
        CollectionUtils.addAllNotNull(headerList, headers);
        var request = SimpleHttpRequest.create(Method.DELETE,
                URI.create(HttpUtils.generateUri(uri, params)));
        request.setHeaders(headerList.toArray(new Header[0]));
        return execute(createDefaultHttpAsyncClient(), request, futureCallback);
    }

    public static Future<SimpleHttpResponse> execute(CloseableHttpAsyncClient asyncClient, SimpleHttpRequest request,
                                                     FutureCallback<SimpleHttpResponse> futureCallback) {
        return asyncClient.execute(request, futureCallback);
    }

    /**
     * 常规 http Post 异步调用
     *
     * @param uri            请求地址
     * @param params         请求参数
     * @param futureCallback 异步回调，如果 为 null　时，直接返回结果
     * @param headers        　请求头
     * @return 结果，如果 futureCallback 不为 null，结果需要在此回调中获得
     */
    public static Future<SimpleHttpResponse> simplePost(String uri, Map<String, ?> params,
                                                        FutureCallback<SimpleHttpResponse> futureCallback,
                                                        Header... headers) {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst());
        CollectionUtils.addAllNotNull(headerList, headers);
        var request = SimpleHttpRequest
                .create(Method.POST, URI.create(HttpUtils.generateUri(uri, params)));
        request.setHeaders(headerList.toArray(new Header[0]));
        return execute(createDefaultHttpAsyncClient(), request, futureCallback);
    }

    /**
     * json http Post 异步调用，添加请求头： Content-Type:application/json;charset=utf-8
     *
     * @param uri            请求地址
     * @param futureCallback 异步回调，如果 为 null　时，直接返回结果
     * @param headers        　请求头
     * @return 结果，如果 futureCallback 不为 null，结果需要在此回调中获得
     */
    public static Future<SimpleHttpResponse> jsonPost(String uri, Object body, FutureCallback<SimpleHttpResponse> futureCallback,
                                                      Header... headers) {
        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.getFirst());
        CollectionUtils.addAllNotNull(headerList, headers);
        var bodyStr = JsonUtils.serialize(body);
        var request = SimpleHttpRequest
                .create(Method.POST, URI.create(uri));
        request.setHeaders(headerList.toArray(new Header[0]));
        if (StringUtils.hasText(bodyStr)) {
            request.setBody(bodyStr, ContentType.APPLICATION_JSON);
        }
        return execute(createDefaultHttpAsyncClient(), request, futureCallback);
    }

//    /**
//     * 附件 http Post 异步调用
//     *
//     * @param uri            请求地址
//     * @param futureCallback 异步回调，如果 为 null　时，直接返回结果
//     * @param headers        　请求头
//     * @return 结果，如果 futureCallback 不为 null，结果需要在此回调中获得
//     */
//    public static String mimePost(String uri, Map<String, ContentBody> contentBody,
//                                  FutureCallback<SimpleHttpResponse> futureCallback, Header... headers) throws IOException {
//        var headerList = ArrayUtils.asArrayList(HttpUtils.DEFAULT_HEADER.get(0));
//        CollectionUtils.addAllNotNull(headerList, headers);
//        var httpPost = HttpUtils.newHttpPost(uri, HttpUtils.multipartHttpEntity(contentBody),
//                headerList.toArray(new Header[0]));
//
//        var request = SimpleHttpRequest
//                .create(Method.POST, URI.create(uri));
//        request.setHeaders(headerList.toArray(new Header[0]));
//        request.setBody("", ContentType.APPLICATION_JSON);
//
//        return execute(createDefaultHttpAsyncClient(), request, futureCallback);
//    }


}
